// Copyright DustedPixels.com 2008. All rights reserved.

package com.dustedpixels.jasmin.unit.compiler;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;

import javax.media.jai.UntiledOpImage;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;

/**
 * @author micapolos@gmail.com (Michal Pociecha-Los)
 */
public final class UnitParserImpl implements UnitParser {
  public Unit parse(String code) throws Exception {
    String[] lines = StringUtils.split(code, '\n');
    Matcher matcher;
    
    // Pass 1: Detect internal units
    Set<String> unitFieldNames = new HashSet<String>();
    for (String line : lines) {
      matcher = Regex.UPDATE_END_POINTS_CALL_LINE_PATTERN.matcher(line);  
      if (matcher.matches()) {
        String fieldName = matcher.group(Regex.UPDATE_END_POINTS_CALL_LINE_TARGET_GROUP);
        unitFieldNames.add(fieldName);
      }
    }

    // Pass 2: Parse
    Map<String, String> importsMap = new HashMap<String, String>();
    
    String className = null;
    String packageName = null;
    List<String> imports = new ArrayList<String>();    
    List<FieldLine> fieldLines = new ArrayList<FieldLine>();
    Arguments constructorArgs = null;
    List<ConstructorLine> constructorLines = new ArrayList<ConstructorLine>();
    List<UpdateEndPointsLine> updateEndPointsLines = new ArrayList<UpdateEndPointsLine>();
    
    String classIndent = null;
    String methodIndent = null;
    String constructorIndent = null;
    
    for (String line : lines) {
      if (constructorIndent != null) {
        // Process constructor code
        if (line.startsWith(constructorIndent + "}")) {
          constructorIndent = null;
        } else {
          if (line.startsWith(constructorIndent)) line = line.substring(constructorIndent.length());
          
          ConstructorLine constructorLine = new ConstructorLine();
          constructorLine.code = line;
          
          matcher = Regex.CONSTRUCTOR_CALL_LINE_PATTERN.matcher(line);
          if (matcher.matches()) {
            String target = matcher.group(Regex.CONSTRUCTOR_CALL_LINE_TARGET_GROUP);
            String unitName = matcher.group(Regex.CONSTRUCTOR_CALL_LINE_CLASS_NAME_GROUP);
            String[] parameters = StringUtils.split(matcher.group(Regex.CONSTRUCTOR_CALL_LINE_PARAMETERS_GROUP), ',');
            for (int i = 0; i < parameters.length; i++) parameters[i] = StringUtils.trim(parameters[i]);
            String qualifiedUnitName = importsMap.get(unitName);
            if (qualifiedUnitName == null) qualifiedUnitName = packageName + "." + unitName;
            NewUnitCall newUnitCall = new NewUnitCall(
                target, qualifiedUnitName, unitName,
                matcher.group(), parameters);

            constructorLine.newUnitCall = newUnitCall;
            constructorLine.type = ConstructorLine.Type.NEW_UNIT_CALL;
          } else {
            constructorLine.type = ConstructorLine.Type.OTHER;
          }
          
          constructorLines.add(constructorLine);
        }
      } else if (methodIndent != null) {
        // Process updateEndPoints() method code
        if (line.startsWith(methodIndent + "}")) {
          methodIndent = null;
        } else {
          if (line.startsWith(methodIndent)) line = line.substring(methodIndent.length());

          UpdateEndPointsLine updateEndPointsLine = new UpdateEndPointsLine();
          updateEndPointsLine.code = line;
          
          // Detect updateEndPoints() call
          matcher = Regex.UPDATE_END_POINTS_CALL_LINE_PATTERN.matcher(line);
          if (matcher.matches()) {
            String target = matcher.group(Regex.UPDATE_END_POINTS_CALL_LINE_TARGET_GROUP);
            updateEndPointsLine.updateEndPointsTarget = target;
            updateEndPointsLine.type = UpdateEndPointsLine.Type.UPDATE_END_POINTS_CALL;
          } else {
            updateEndPointsLine.type = UpdateEndPointsLine.Type.OTHER;
          }

          updateEndPointsLines.add(updateEndPointsLine);
        }
      } else if (classIndent != null) {
        // Match field declaration
        matcher = Regex.FIELD_DECLARATION_PATTERN.matcher(line);
        if (matcher.matches()) {
          String name = matcher.group(Regex.FIELD_DECLARATION_NAME_GROUP);
          FieldLine.Type type = unitFieldNames.contains(name) 
              ? FieldLine.Type.UNIT 
              : FieldLine.Type.OTHER;
          FieldLine fieldLine = new FieldLine(type, name, matcher.group());
          fieldLines.add(fieldLine);
        }
        
        // Match update method declaration
        matcher = Regex.METHOD_DECLARATION_PATTERN.matcher(line);
        if (matcher.matches()) {
          String name = matcher.group(Regex.METHOD_DECLARATION_NAME_GROUP);
          if (!name.equals("updateEndPoints")) {
            throw new RuntimeException("No methods allowed except update()");
          }
          methodIndent = matcher.group(Regex.METHOD_DECLARATION_INDENT_GROUP);
        }
        
        // Match constructor declaration
        matcher = Regex.CONSTRUCTOR_DECLARATION_PATTERN.matcher(line);
        if (matcher.matches()) {
          constructorIndent = matcher.group(Regex.CONSTRUCTOR_DECLARATION_INDENT_GROUP);
          String[] args =
              StringUtils.split(matcher.group(Regex.CONSTRUCTUR_DECLARATION_ARGS_GROUP), ',');
          String[] argTypesAndNames = new String[2 * args.length];
          for (int i = 0; i < args.length; i++) {
            String[] words = StringUtils.split(args[i]);
            argTypesAndNames[2 * i] = words[0];
            argTypesAndNames[2 * i + 1] = words[1];
          }
          constructorArgs = new Arguments(argTypesAndNames);
        }
      } else {
        // Match package declaration
        matcher = Regex.PACKAGE_PATTERN.matcher(line);
        if (matcher.matches()) {
          packageName = matcher.group(Regex.PACKAGE_NAME_GROUP);
        }

        // Match import statements
        matcher = Regex.IMPORT_PATTERN.matcher(line);
        if (matcher.matches()) {
          String anImport = matcher.group(Regex.IMPORT_NAME_GROUP);
          imports.add(anImport);
          importsMap.put(anImport.substring(anImport.lastIndexOf('.') + 1), anImport);
        }
        
        // Match Unit class declaration
        matcher = Regex.UNIT_CLASS_DECLARATION_PATTERN.matcher(line);
        if (matcher.matches()) {
          className = matcher.group(Regex.UNIT_CLASS_DECLARATION_NAME_GROUP);
          classIndent = ""; // Assume indent of zero.
        }
      }        
    };
    
    if (className == null) {
      throw new RuntimeException("No class name found");
    }
    
    Unit unit = new Unit(packageName, className);
    unit.imports = imports.toArray(new String[imports.size()]);
    unit.fieldLines = fieldLines.toArray(new FieldLine[0]);
    unit.constructorLines = constructorLines.toArray(new ConstructorLine[0]);
    unit.constructorArgs = constructorArgs;
    unit.updateEndPointsLines = updateEndPointsLines.toArray(new UpdateEndPointsLine[0]);
    unit.unitFieldNames = unitFieldNames;
    
    return unit;
  }
  
  public static void main(String[] args) throws Exception {
    InputStream input = new FileInputStream("src/impl/com/dustedpixels/jasmin/unit/ula/ULA.java");
    Unit unit = new UnitParserImpl().parse(IOUtils.toString(input));
    input.close();
    
    System.out.println(new ProcessCodegenImpl().generateCode(unit));
  }
}
